/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2013 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#ifndef SX_SIMULATION_OBJECT_H
#define SX_SIMULATION_OBJECT_H

/* Abstract simulation object reference type. */
#include <sxAbstractSimulationObject.h>

/* Visual and physical representation of a simulation object. */
#include <sxGraphicsArchetype.h>

/* Storage type related to loading and storing simulation objects. */
#include <sxSimulationObjectStorage.h>

/* Abstract renderer type for rendering simulations objects. */
#include <sxSimulationObjectRenderer.h>

using Sx::Simulation::Storage::SimulationObjectStorage;
using Sx::Graphics::Archetypes::GraphicsArchetype;

namespace Sx {
namespace Simulation {

template <typename ObjectType, typename PhysicalRepresentationType>
class SimulationObject : public AbstractSimulationObject {
public:
	SimulationObject();
	virtual ~SimulationObject();

	/* 
	 * This function acts as a 'set' function. It sets the internal object,
	 * the render, and the physical implementation of the graphics archetype.
	 */
	bool setArchetype(const std::shared_ptr<ObjectType>& object, const std::shared_ptr<Objects::Rendering::RendererImplementation>& renderer, const std::shared_ptr<PhysicalRepresentationType>& physicalRepresentation);

	/* 
	 * Static function that can be used for loading a SimulationObject from
	 * a given storage type and filename. The renderer of this new type must be
	 * provided at this time of construction. The storage type is responsible
	 * for creating each component of the loaded SimulationObject: The renderer,
	 * physical representation and the abstract object type that the
	 * SimulationObject represents.
	 */
	template <typename StorageObjectType>
	static std::shared_ptr<StorageObjectType> Load(const SimulationObjectStorage<StorageObjectType, ObjectType, PhysicalRepresentationType>* const storageType, const std::shared_ptr<Objects::Rendering::RendererImplementation>& renderer, const String& filename);
	
	template <typename StorageObjectType>
	bool save(const std::shared_ptr<SimulationObjectStorage<StorageObjectType, ObjectType, PhysicalRepresentationType> >& storageType, const String& filename);
	
	bool construct();
	void update(double dt);
	void render() const;

	std::shared_ptr<ObjectType> getObject() const;
	std::shared_ptr<PhysicalRepresentationType> getPhysicalRepresentation() const;
	std::shared_ptr<Objects::Rendering::RendererImplementation> getRenderer() const;

protected:
	/* 
	 * This rendering implementation must be an abstract templated type so that
	 * the type of renderer can be changed; but since the render needs to know
	 * about the object and the physical representation, it must be provided
	 * through a templated renderer implementation.
	 */
	GraphicsArchetype<ObjectType, Objects::Rendering::RendererImplementation, double, PhysicalRepresentationType> representation;
};

template <typename ObjectType, typename PhysicalRepresentationType>
SimulationObject<ObjectType, PhysicalRepresentationType>::SimulationObject() {
	
}

template <typename ObjectType, typename PhysicalRepresentationType>
bool SimulationObject<ObjectType, PhysicalRepresentationType>::setArchetype(const std::shared_ptr<ObjectType>& object, const std::shared_ptr<Objects::Rendering::RendererImplementation>& renderer, const std::shared_ptr<PhysicalRepresentationType>& physicalRepresentation) {
	if ( object == nullptr ) return false;
	else this->representation.setObject(object);

	if ( renderer == nullptr ) return false;
	else this->representation.setRenderer(renderer);
	
	if ( physicalRepresentation == nullptr ) return false;
	else this->representation.setPhysicalRepresentation(physicalRepresentation);

	return true;
}

template <typename ObjectType, typename PhysicalRepresentationType>
SimulationObject<ObjectType, PhysicalRepresentationType>::~SimulationObject() {}

template <typename ObjectType, typename PhysicalRepresentationType>
template <typename StorageObjectType>
std::shared_ptr<StorageObjectType> SimulationObject<ObjectType, PhysicalRepresentationType>::Load(const SimulationObjectStorage<StorageObjectType, ObjectType, PhysicalRepresentationType>* const storageType, const std::shared_ptr<Objects::Rendering::RendererImplementation>& renderer, const String& filename) {
	if ( storageType == nullptr ) {
		std::cerr << "[SceneObject:Load] Error: Storage type cannot be a nullptr." << std::endl;
		std::cerr << "  Aborting scene object load; returning nullptr" << std::endl;
		return nullptr;
	}

	if ( filename.length() == 0 ) {
		std::cerr << "[SceneObject:Load] Error: Filename of length 0." << std::endl;
		std::cerr << "  Aborting scene object load; returning nullptr" << std::endl;
		return nullptr;
	}

	if ( renderer == nullptr ) {
		std::cerr << "[SceneObject:Load] Error: Provided renderer implementation cannot be a nullptr." << std::endl;
		std::cerr << "  Aborting scene object load; returning nullptr" << std::endl;
		return nullptr;
	}

	return storageType->load(renderer, filename);
}

template <typename ObjectType, typename PhysicalRepresentationType>
template <typename StorageObjectType>
bool SimulationObject<ObjectType, PhysicalRepresentationType>::save(const std::shared_ptr<SimulationObjectStorage<StorageObjectType, ObjectType, PhysicalRepresentationType> >& storageType, const String& filename) {
	if ( storageType == nullptr ) {
		std::cerr << "[SceneObject:save] Error: Storage type cannot be a nullptr." << std::endl;
		std::cerr << "  Aborting scene object save; returning nullptr" << std::endl;
		return nullptr;
	}

	if ( storageType == nullptr ) {
		std::cerr << "[SceneObject:save] Error: Filename of length 0." << std::endl;
		std::cerr << "  Aborting scene object save; returning nullptr" << std::endl;
		return nullptr;
	}

	return storageType->save(filename, this);
}

template <typename ObjectType, typename PhysicalRepresentationType>
bool SimulationObject<ObjectType, PhysicalRepresentationType>::construct() {
	return this->representation.construct();
}

template <typename ObjectType, typename PhysicalRepresentationType>
void SimulationObject<ObjectType, PhysicalRepresentationType>::update(double dt) {
	this->representation.update(dt);
}

template <typename ObjectType, typename PhysicalRepresentationType>
void SimulationObject<ObjectType, PhysicalRepresentationType>::render() const {
	this->representation.render();
}

template <typename ObjectType, typename PhysicalRepresentationType>
std::shared_ptr<ObjectType> SimulationObject<ObjectType, PhysicalRepresentationType>::getObject() const {
	return this->representation.getObject();
}

template <typename ObjectType, typename PhysicalRepresentationType>
std::shared_ptr<PhysicalRepresentationType> SimulationObject<ObjectType, PhysicalRepresentationType>::getPhysicalRepresentation() const {
	return this->representation.getPhysicalRepresentation();
}

template <typename ObjectType, typename PhysicalRepresentationType>
std::shared_ptr<Sx::Simulation::Objects::Rendering::RendererImplementation> SimulationObject<ObjectType, PhysicalRepresentationType>::getRenderer() const {
	return this->representation.getRenderer();
}

}

}


#endif
